package net.protocol.diameter.avp;

import net.protocol.common.util.Ints;

import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * <pre>
 *   OctetString
 *       The data contains arbitrary data of variable length.  Unless
 *       otherwise noted, the AVP Length field MUST be set to at least 8
 *       (12 if the 'V' bit is enabled).  AVP Values of this type that are
 *       not a multiple of four-octets in length is followed by the
 *       necessary padding so that the next AVP (if any) will start on a
 *       32-bit boundary.
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
public class OctetString extends AbstractAVP<String> {
	private final byte[] data;

    /**
     * @param code AVP Code
     * @param data AVP Data
     */
    public OctetString(int code, String data) {
        this(code, data.getBytes());
    }

    /**
     * @param code AVP Code
     * @param data AVP Data
     */
    protected OctetString(int code, byte[] data) {
        this(code, DEFAULT_VENDOR_ID, data);
    }

    /**
     * @param code     AVP Code
     * @param vendorId Vendor-ID
     * @param data     AVP Data
     */
    public OctetString(int code, int vendorId, String data) {
        this(code, vendorId, data.getBytes());
    }

    /**
     * @param code     AVP Code
     * @param vendorId Vendor-ID
     * @param data     AVP Data
     */
    protected OctetString(int code, int vendorId, byte[] data) {
    	// The length of the padding is not reflected in the AVP Length field.
        super(code, vendorId, data.length);

        this.data = data;
    }

    /**
     * Creates a new <code>OctetString</code> with the given DiameterAVPHeader and source ByteBuffer
     *
     * @param header Diameter AVP Header
     * @param src The buffer from which bytes are to be retrieved
     */
    OctetString(DiameterAVPHeader header, ByteBuffer src) {
        super(header, src);

        final byte[] dst = new byte[getDataLength()];
        src.get(dst);
        this.data = dst;

        // skip a number of zero-valued bytes padding data
        int paddingSize = Ints.paddingSize(data.length);
        if (paddingSize > 0) src.position(src.position() + paddingSize);
    }

    @Override
    public ByteBuffer byteMe(ByteBuffer dst) {
        super.byteMe(dst);
        
        dst.put(data);
        // padding with zeroes to the next 32-bit boundary
        int paddingSize = Ints.paddingSize(data.length);
        if (paddingSize > 0) dst.put(empty, 0, paddingSize);
        
        return dst;
    }

    @Override
    public String getData() {
        return new String(data);
    }

    @Override
    public byte[] getDataBytes() {
    	return data;
    }
    
    @Override
    public final boolean isGroup() {
        return false;
    }

    @Override
    public DataFormat getType() {
        return DataFormat.OctetString;
    }

    @Override
    public String getName() {
        return DataFormat.OctetString.name();
    }

    @Override
    protected void detail(StringBuilder buf) {
        buf.append(new String(data));
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        OctetString other = (OctetString) obj;
        if (!Arrays.equals(data, other.data))
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + Arrays.hashCode(data);
        return result;
    }
}
